package server;

import database.DataBase;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * @author Hsu琛君珩
 * @ClassName:MainServer
 * @date 2023-12-19
 * @apiNote
 * @Version: v1.0
 */

/**
 * 五子棋游戏的主服务器类。
 * 负责监听和处理客户端的连接请求。
 */
public class MainServer {

    private static MainServer instance;
    private static final int PORT = 8080; // 主服务器监听的端口
    private static ServerSocket serverSocket; // 主服务器的ServerSocket
    private static final int roomNum = 12; // 初始化的房间数量
    private static HashMap<Integer, RoomServer> roomList; // 房间列表
    private static ArrayList<Integer> roomMemberNum; // 房间内人数列表

    /**
     * 私有构造函数。
     * 该构造函数用于初始化主服务器的关键组件。它首先尝试创建一个ServerSocket来监听特定的端口。
     * 接着，它初始化房间列表和房间成员数列表，为每个预设房间创建一个RoomServer实例并将其存储。
     * 最后，它调用startThread方法来启动一个线程，这个线程将负责持续监听并接受客户端的连接请求。
     */
    private MainServer() {
        try {
            serverSocket = new ServerSocket(PORT); // 在指定端口创建ServerSocket监听客户端请求
            roomList = new HashMap<>(); // 初始化房间列表
            roomMemberNum = new ArrayList<>(); // 初始化房间成员数量列表
            for (int i = 0; i < roomNum; i++) {
                roomList.put(i, new RoomServer(i)); // 为每个房间创建一个RoomServer实例
                roomMemberNum.add(0); // 初始化房间成员数为0
            }
            startThread(); // 启动主服务器监听线程
        } catch (IOException e) {
            e.printStackTrace(); // 打印异常信息
        }
    }

    /**
     * 获取MainServer的单例实例。
     *
     * @return MainServer的实例
     */
    public static MainServer getInstance() {
        if (instance == null) {
            instance = new MainServer();
        }
        return instance;
    }

    /**
     * 启动主服务端线程，持续接受客户端请求。
     * 这个方法创建并启动了一个新线程，该线程不断地监听并接受来自客户端的连接。
     * 一旦接受到一个连接，它就会调用handleClientRequest方法来处理该连接。
     */
    private void startThread() {
        new Thread(() -> {
            System.out.println("主服务端监听线程启动");
            while (true) {
                try {
                    Socket socket = serverSocket.accept();  // 监听并接受客户端的连接
                    handleClientRequest(socket); // 处理接收到的客户端请求
                } catch (IOException e) {
                    e.printStackTrace(); // 打印异常信息
                }
            }
        }).start();
    }

    /**
     * 处理客户端请求的方法。
     * 此方法在新线程中执行，它读取从客户端发送的信息，并根据请求的不同执行相应的操作。
     * 它支持处理多种类型的请求，包括获取房间成员数量、用户注册验证、用户登录验证、设置房间ID，以及处理客户端退出房间的请求。
     *
     * @param socket 客户端的Socket连接
     */
    private void handleClientRequest(Socket socket) {
        new Thread(() -> {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                 PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

                String line = in.readLine(); // 读取客户端发送的请求信息
                System.out.println("收到客户端消息: " + line);

                // 根据请求的不同类型，调用相应的处理方法
                if (line.contains("GET_ROOM_MEMBER_NUM")) {
                    getRoomMember(socket); // 处理获取房间成员数量的请求
                } else if (line.startsWith("REGISTER_VERIFY")) {
                    registerVerify(out, line); // 处理用户注册验证的请求
                } else if (line.startsWith("LOGIN_VERIFY")) {
                    loginVerify(out, line); // 处理用户登录验证的请求
                } else if (line.startsWith("SET_ROOMID")) {
                    setRoomID(out, line); // 处理设置房间ID的请求
                } else if (line.startsWith("EXIT")) {
                    clientExitRoom(line); // 处理客户端退出房间的请求
                }
                // 其他请求处理留空，可以根据需要添加
            } catch (IOException e) {
                e.printStackTrace(); // 打印异常信息
            }
        }).start();
    }

    /**
     * 客户端退出房间的处理。
     * 当客户端发送退出房间的请求时，此方法会减少对应房间的成员数量并重新初始化该房间。
     *
     * @param line 客户端发送的退出房间请求信息，包含房间ID。
     */
    private void clientExitRoom(String line) {
        String[] info = line.split(":");
        int roomId = Integer.parseInt(info[1]);
        roomMemberNum.set(roomId, roomMemberNum.get(roomId) - 1); // 减少房间成员数量
        roomList.get(roomId).init(); // 重新初始化该房间
    }

    /**
     * 设置客户端所选房间ID，并返回新的房间端口号。
     * 当客户端请求加入房间时，此方法会为其分配一个房间端口，并更新房间内的成员数量。
     *
     * @param out  客户端输出流，用于发送响应。
     * @param line 客户端发送的设置房间ID请求信息，包含房间ID。
     */
    private void setRoomID(PrintWriter out, String line) {
        String[] info = line.split(":");
        int roomId = Integer.parseInt(info[1]);
        roomList.get(roomId).startServer(PORT + 1 + roomId); // 启动房间服务器
        roomMemberNum.set(roomId, roomMemberNum.get(roomId) + 1); // 增加房间成员数量
        int newRoomPort = PORT + 1 + roomId; // 计算新房间端口号
        out.println("a:" + newRoomPort); // 发送新房间端口号给客户端
    }

    /**
     * 处理用户登录验证。
     * 接收客户端的登录请求，并通过数据库验证用户名和密码，返回验证结果。
     *
     * @param out  客户端输出流，用于发送验证结果。
     * @param line 客户端发送的登录验证请求信息，包含用户名和密码。
     */
    private void loginVerify(PrintWriter out, String line) {
        String[] info = line.split(":");
        String username = info[1];
        String password = info[2];
        boolean isLoginSuccess = DataBase.getInstance().loginVerify(username, password);
        out.println(isLoginSuccess ? "TRUE" : "FALSE"); // 发送验证结果
    }

    /**
     * 处理用户注册验证。
     * 接收客户端的注册请求，并通过数据库验证用户名是否可用，返回验证结果。
     *
     * @param out  客户端输出流，用于发送验证结果。
     * @param line 客户端发送的注册验证请求信息，包含用户名和密码。
     */
    private void registerVerify(PrintWriter out, String line) {
        String[] info = line.split(":");
        String username = info[1];
        String password = info[2];
        boolean isRegisterSuccess = DataBase.getInstance().registerVerify(username, password);
        out.println(isRegisterSuccess ? "TRUE" : "FALSE"); // 发送验证结果
    }

    /**
     * 获取房间成员数量，并发送给客户端。
     * 当客户端请求获取房间成员数量时，此方法会将当前所有房间的成员数量序列化并发送给客户端。
     *
     * @param socket 客户端的Socket连接。
     * @throws IOException 输入输出异常。
     */
    private void getRoomMember(Socket socket) throws IOException {
        ObjectOutputStream o = new ObjectOutputStream(socket.getOutputStream());
        o.writeObject(roomMemberNum); // 将房间成员数量列表发送给客户端
    }

}
